package com.vivz.guess.what;

import static com.vivz.guess.what.util.GuessWhatDictionary.KEYWORDS;
import static com.vivz.guess.what.util.GuessWhatUtil.EMPTY_STRING;
import static com.vivz.guess.what.util.GuessWhatUtil.calculateClue;
import static com.vivz.guess.what.util.GuessWhatUtil.calculateWeight;
import static com.vivz.guess.what.util.GuessWhatUtil.getStringFromSet;
import static com.vivz.guess.what.util.GuessWhatUtil.isComprisedOnlyOfLetters;
import static com.vivz.guess.what.util.GuessWhatUtil.isInDictionary;
import static com.vivz.guess.what.util.GuessWhatUtil.isMadeOfUniqueCharacters;
import static com.vivz.guess.what.util.GuessWhatUtil.isNCharactersLong;
import static com.vivz.guess.what.util.GuessWhatUtil.isValid;
import static com.vivz.guess.what.util.GuessWhatUtil.linkify;
import static com.vivz.guess.what.util.GuessWhatUtil.repeatedCharacters;
import static com.vivz.guess.what.util.GuessWhatUtil.shuffle;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.Set;

import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.Html;
import android.text.Spanned;
import android.text.TextUtils;
import android.view.Menu;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.Chronometer;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.google.ads.AdRequest;
import com.google.ads.AdSize;
import com.google.ads.AdView;
import com.vivek.guess.what.calculator.All0Calculator;
import com.vivek.guess.what.calculator.AllXCalculator;
import com.vivek.guess.what.calculator.Calculator;
import com.vivek.guess.what.calculator.ZeroPlus0Calculator;
import com.vivek.guess.what.calculator.ZeroPlusXCalculator;
import com.vivek.guess.what.calculator.ZeroWeightCalculator;
import com.vivz.guess.what.vo.Clue;
import com.vivz.guess.what.vo.Game;
import com.vivz.guess.what.vo.Guess;
import com.vivz.guess.what.vo.ProbabilityMatrix;

/**
 * TODO: 1. Fix the gravity issue <br>
 * Once guess entered, the user should be able to scroll through the list of guess without the guesses text rolling back
 * to the bottom after the user has scrolled up.
 * 
 * @author Vivek
 */
public class MainActivity extends Activity {

	private static final String DEFAULT_ENCODING = "UTF-8";
	private static final String DEFAULT_MIME_TYPE = "text/html";
	private static final All0Calculator ALL_0_CALCULATOR = new All0Calculator();
	private static final AllXCalculator ALL_X_CALCULATOR = new AllXCalculator();
	private static final ZeroPlus0Calculator ZERO_PLUS_0_CALCULATOR = new ZeroPlus0Calculator();
	private static final ZeroPlusXCalculator ZERO_PLUS_X_CALCULATOR = new ZeroPlusXCalculator();
	private static final ZeroWeightCalculator ZERO_WEIGHT_CALCULATOR = new ZeroWeightCalculator();
	private List<Calculator> calculators;

	// //////////////////
	// Screen Elements
	// //////////////////

	private Chronometer chronometer;
	private WebView guessesText;
	private EditText guessText;
	private TextView greetingText;
	private LinearLayout adSectionLayout;
	private AdView adView;

	private TextView definiteInsiders;
	private TextView probableInsiders;
	private TextView definiteOutsiders;

	private Button buttonNewGame;
	private LinearLayout guessInputSection;

	/**
	 * Cheat code to reveal the keyword being played for within the game.
	 */
	private static final String CHEAT_CODE = "????";

	/**
	 * Index of the keyword that the player is playing for in the current game.
	 */
	private static int CURRENT_KEYWORD_INDEX;

	/**
	 * URL for dictionary where the player can look up the meaning of a word.
	 */
	private static URL DICTIONARY_URL = null;

	/**
	 * <pre>
	 * The game object that contains
	 * - guesses list
	 * - probability matrix
	 * </pre>
	 */
	private Game game;

	private static final Random RANDOM = new Random();

	/**
	 * Set of indexes of all keywords that the player has guessed or played for.
	 */
	private static final Set<Integer> USED_WORDS_INDICES = new HashSet<Integer>();

	static {
		CURRENT_KEYWORD_INDEX = nextIndex(-1);
		try {
			DICTIONARY_URL = new URL("http://www.merriam-webster.com/dictionary");
		} catch (final MalformedURLException e) {

		}
	}

	/**
	 * Gets the next valid keyword's index from the store of keywords. TODO: This needs to save and retrieve the used
	 * words indices from a persisted storage.
	 * 
	 * @return the next index of an un-guessed word.
	 */
	private static int nextIndex(final int currentIndex) {

		final int randomInt = RANDOM.nextInt(KEYWORDS.length);
		final int nextInt = randomInt < 0 ? randomInt * -1 : randomInt;
		final boolean isNewWord = USED_WORDS_INDICES.add(nextInt);

		if (isNewWord && isValid(KEYWORDS[nextInt])) {
			return nextInt;
		}

		return nextIndex(nextInt);
	}

	/**
	 * Returns a message based on where the input was wrong.
	 * 
	 * @param guessWord
	 * @return
	 */
	private String getErrorMessage(final String guessWord) {
		if (!isComprisedOnlyOfLetters(guessWord)) {
			return getString(R.string.error_only_letters_allowed);

		} else if (!isNCharactersLong(guessWord, 4)) {
			return getString(R.string.error_4_characters_only);

		} else if (!isMadeOfUniqueCharacters(guessWord)) {
			return getString(R.string.error_no_repetition_allowed, repeatedCharacters(guessWord), guessWord);

		} else if (!isInDictionary(guessWord)) {
			return getString(R.string.error_out_of_dictionary, guessWord);
		}

		return "rEaLly - \"" + guessWord + "\" ?? [U R B3tTer +H@n +H!S !!]\n";
	}

	/**
	 * Hides the keyboard.
	 */
	@SuppressWarnings("unused")
	private void hideKeyboard() {

		// Hide the keyboard after a guess is entered.
		// TODO: Need to find a more user-friendly use case here
		final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
		imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
	}

	/**
	 * Straightforward comparison of the guess against the keyword.
	 * 
	 * @param guessWord
	 * @return
	 */
	private boolean isGuessCorrect(final String guessWord) {
		final boolean correct;
		if (TextUtils.isEmpty(guessWord)) {
			correct = false;
		} else {
			correct = KEYWORDS[CURRENT_KEYWORD_INDEX].equalsIgnoreCase(guessWord.trim());
		}
		return correct;
	}

	/**
	 * TODO: Implement this method to check whether the Guesses text did overflow with content visible on screen
	 * 
	 * @return
	 */
	private boolean isTextOverflown() {
		return true;
	}

	/**
	 * Called when the user hits the Guess button.
	 * 
	 * @param view
	 */
	public void newGame(final View view) {

		final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
		final String userName = prefs.getString("example_text", "Vivek");

		// Set the appropriate starting Status message
		setStatusMessage(getString(R.string.hello_user, userName));

		// Reset the timer/chronometer
		resetChronometer();

		// Clear the Guess text box
		guessText.setText(EMPTY_STRING);

		// Clear the Guesses text box
		guessesText.loadData(EMPTY_STRING, DEFAULT_MIME_TYPE, DEFAULT_ENCODING);

		// Show the Guess section and hide the New Game button
		toggleButtons(false);

		// Set the appropriate counter and keyword-index
		CURRENT_KEYWORD_INDEX = nextIndex(CURRENT_KEYWORD_INDEX);

		// Reset the guesses set
		game = new Game(4);
		calculators = new ArrayList<Calculator>();
		calculators.add(ZERO_WEIGHT_CALCULATOR);
		calculators.add(ALL_0_CALCULATOR);
		calculators.add(ALL_X_CALCULATOR);
		calculators.add(ZERO_PLUS_0_CALCULATOR);
		calculators.add(ZERO_PLUS_X_CALCULATOR);

		updateHintView();
	}

	@Override
	public void onCreate(final Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_guess_what);

		// Initialize screen elements
		chronometer = (Chronometer) findViewById(R.id.chronometer);
		guessesText = (WebView) findViewById(R.id.guesses_text);
		guessText = (EditText) findViewById(R.id.guess_text);
		greetingText = (TextView) findViewById(R.id.greeting_text);

		adSectionLayout = (LinearLayout) findViewById(R.id.ad_section);
		loadAdView();

		definiteInsiders = (TextView) findViewById(R.id.definite_insiders);
		probableInsiders = (TextView) findViewById(R.id.probable_insiders);
		definiteOutsiders = (TextView) findViewById(R.id.definite_outsiders);

		buttonNewGame = (Button) findViewById(R.id.button_new_game);
		guessInputSection = (LinearLayout) findViewById(R.id.guess_input_section);

		// This will go away once we start pulling the used word indices from a persisted data storage.
		USED_WORDS_INDICES.clear();

		newGame(null);
	}

	private void loadAdView() {

		// Create the adView
		adView = new AdView(this, AdSize.SMART_BANNER, getString(R.string.admob_id));

		// Add the adView to it
		adSectionLayout.addView(adView);

		// Initiate a generic request to load it with an ad
		adView.loadAd(new AdRequest());

	}

	@Override
	public boolean onCreateOptionsMenu(final Menu menu) {
		getMenuInflater().inflate(R.menu.activity_guess_what, menu);

		// do not show a menu for now on press of menu button on phone
		return false;
	}

	/**
	 * Called when the user hits the Guess button.
	 * 
	 * @param view
	 */
	public void registerGuess(final View view) {

		final String guessWord = guessText.getText().toString().toUpperCase(Locale.US).trim();

		// hide the keyboard
		// hideKeyboard();

		/*
		 * 1. Validate the entry
		 */
		if (isValid(guessWord)) {

			/*
			 * If the word is guessed right - - show congratulatory message - hide the 'Guess Again' button - show the
			 * 'New Game' button - stop the counter
			 */
			if (isGuessCorrect(guessWord)) {

				// Show the game over bar
				guessesText.loadData("-- -- -- -- --", DEFAULT_MIME_TYPE, DEFAULT_ENCODING);

				// Set the congratulatory message
				final String congratulatoryMessage = "***" + linkify(guessWord, DICTIONARY_URL) + "***\nYou got it in "
						+ (game.getGuesses().size() + 1) + " guesses [" + chronometer.getText() + "] !!!";
				setStatusMessage(congratulatoryMessage);

				toggleButtons(true);

				chronometer.stop();

			} else {
				final Guess guess = new Guess(4, guessWord, calculateClue(guessWord, KEYWORDS[CURRENT_KEYWORD_INDEX]));

				if (!game.addGuess(guess)) {
					final String message = getString(R.string.talk_back_redundant_guess, guessWord);
					setStatusMessage(message);

				} else {

					showAnEncouragingStatusMessage(guess);

					/*
					 * 3. If the entry is valid, - if its the first guess - start the counter - increase the count of
					 * guesses - check if the word is found -- if the word is not found update the guesses text box and
					 * apply the correct X . for the guess.
					 */
					if (game.getGuesses().size() == 0) {
						resetChronometer();
						chronometer.start();
					}

					updateGuessesWebView();

					deduceHints();
					updateHintView();
				}
			}

		} else {

			// cheat-code :)
			if (guessWord.contains(CHEAT_CODE)) {
				setStatusMessage(KEYWORDS[CURRENT_KEYWORD_INDEX]);
			} else {
				setStatusErrorMessage(guessWord);
			}
		}

		guessText.setText(EMPTY_STRING);
	}

	/**
	 * Updates the TextViews corresponding to DOs, PIs and DIs and Probability Map
	 */
	private void updateHintView() {
		final ProbabilityMatrix matrix = game.getMatrix();
		definiteInsiders.setText(Html.fromHtml("DI: " + getStringFromSet(matrix.getDefiniteInsiders().keySet())));
		probableInsiders.setText(Html.fromHtml("PI: " + getStringFromSet(matrix.getProbableInsiders())));
		definiteOutsiders.setText(Html.fromHtml("DO: " + getStringFromSet(matrix.getDefiniteOutsiders())));
	}

	private void deduceHints() {
		for (final Calculator calculator : calculators) {
			calculator.calculate(game);
		}
	}

	/**
	 * Reset the timer.
	 */
	private void resetChronometer() {
		chronometer.setBase(SystemClock.elapsedRealtime());
	}

	/**
	 * Called when the user scrolls through his guesses.
	 * 
	 * @param view
	 */
	public void resetGravity(final View view) {
		if (isTextOverflown()) {
			/************ DISABLED TEMPORARILY ************/
			// // Reset the gravity of the guesses text view
			// guessesText.setGravity(Gravity.NO_GRAVITY);
		}
	}

	/**
	 * Sets appropriate error message in the status based on the guess
	 * 
	 * @param guessWord
	 */
	private void setStatusErrorMessage(final String guessWord) {
		final String errorMessage = getErrorMessage(guessWord);
		setStatusMessage(errorMessage);
	}

	/**
	 * Sets a message in the status.
	 * 
	 * @param message
	 */
	private void setStatusMessage(final String message) {
		final Spanned html = Html.fromHtml(message);
		greetingText.setText(html);
	}

	/**
	 * Shows a status message to encourage the user based on the guess.
	 * 
	 * @param guess
	 */
	private void showAnEncouragingStatusMessage(final Guess guess) {

		String talkBackText = "";

		final Clue clue = guess.getClue();
		if (clue.hasDash()) {
			talkBackText = getString(R.string.talk_back_dash);

		} else if (clue.hasCrosses() && clue.hasDots()) {
			final int countX = clue.getCountX();
			final int countO = clue.getCount0();
			if (countX + countO == 4) {
				talkBackText = getString(R.string.talk_back_mix_4);

			} else {
				talkBackText = getString(R.string.talk_back_mix, countX, countO);
			}

		} else if (!clue.hasCrosses() && clue.hasDots()) {
			talkBackText = getString(R.string.talk_back_dots);

		} else if (!clue.hasDots() && clue.hasCrosses()) {
			talkBackText = getString(R.string.talk_back_crosses);
		}

		setStatusMessage(talkBackText);
	}

	/**
	 * Toggle the display between "Guess" and "New Game" buttons
	 * 
	 * @param showNewButton
	 */
	private void toggleButtons(final boolean showNewButton) {
		if (showNewButton) {
			guessInputSection.setVisibility(View.GONE);
			buttonNewGame.setVisibility(View.VISIBLE);
		} else {
			guessInputSection.setVisibility(View.VISIBLE);
			buttonNewGame.setVisibility(View.GONE);
		}
	}

	private void updateGuessesWebView() {
		final StringBuffer htmlBuffer = new StringBuffer();

		htmlBuffer.append("<html>");
		htmlBuffer.append("<head></head>");
		htmlBuffer.append("<body>");

		int count = 1;
		for (final Guess guess : game.getGuesses()) {

			final String guessWord = guess.getWord();
			htmlBuffer.append(count++);
			htmlBuffer.append(":");
			htmlBuffer.append(linkify(guessWord, DICTIONARY_URL));
			htmlBuffer.append(" [");
			htmlBuffer.append(shuffle(calculateWeight(KEYWORDS[CURRENT_KEYWORD_INDEX], guessWord)));
			htmlBuffer.append("]");
			htmlBuffer.append("<br/>");
		}
		htmlBuffer.append("</body>");
		htmlBuffer.append("</html>");

		guessesText.loadData(htmlBuffer.toString(), DEFAULT_MIME_TYPE, DEFAULT_ENCODING);
	}
}
